//
//  ViewController.swift
//  CrashInterpreter
//
//  Created by layne on 2020/9/11.
//  Copyright © 2020 layne. All rights reserved.
//

import Cocoa

class MasterViewController: NSViewController {
    var dsymPath: String = ""
    var projectName: String = ""
    @IBOutlet weak var dsymPathLabel: NSTextField! //符号表路径
    @IBOutlet weak var infoLabel: NSTextField!     //提示信息
    @IBOutlet weak var sourceField: NSScrollView!  //源field
    @IBOutlet weak var resultField: NSScrollView! {
        didSet{
            let textView = resultField.documentView as! NSTextView
            textView.isEditable = false
        }
    }//结果field
    @IBOutlet weak var indicator: NSProgressIndicator!
    
    var sourceTextView: NSTextView {
        sourceField.documentView as! NSTextView
    }
    var resultTextView: NSTextView {
        resultField.documentView as! NSTextView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

//MARK:- Event
    // 选择符号表
    @IBAction func selectDSYM(_ sender: NSButton) {
        let openPanel = NSOpenPanel()
        openPanel.prompt = "打开"
        openPanel.directoryURL = nil;
        openPanel.beginSheetModal(for: NSApplication.shared.keyWindow!) { [unowned self](response) in
            if response == .OK {
                let fileUrl = openPanel.urls.first!
                let filePathStr = fileUrl.absoluteString
                let startIndex = filePathStr.index(filePathStr.startIndex, offsetBy: 7)
                let endIndex = filePathStr.index(filePathStr.startIndex, offsetBy: filePathStr.count)
                self.dsymPath = filePathStr[startIndex..<endIndex].removingPercentEncoding! //path中包含中文时进行转码
                self.dsymPathLabel.attributedStringValue = NSAttributedString(string: self.dsymPath)
                
                let start = self.dsymPath.index(self.dsymPath.lastIndex(of: "/")!, offsetBy: 1)
                let end = self.dsymPath.index(self.dsymPath.startIndex, offsetBy: self.dsymPath.count)
                self.projectName = String(self.dsymPath[start..<end])
            }
        }
    }
    
    // 解析
    @IBAction func interpret(_ sender: NSButton) {
        infoLabel.attributedStringValue = NSAttributedString(string: "") // 清空
        
        var sourceStr = sourceTextView.textStorage!.string
        sourceStr = sourceStr.trimmingCharacters(in: .whitespacesAndNewlines)
        
        guard sourceStr.count > 0 else {
            infoLabel.attributedStringValue = NSAttributedString(string: "请输入要解析的日志信息", attributes: [NSAttributedString.Key.foregroundColor: NSColor.red])
            return
        }
        guard dsymPath.count > 0 else {
            infoLabel.attributedStringValue = NSAttributedString(string: "请先选择dSYM文件", attributes: [NSAttributedString.Key.foregroundColor: NSColor.red])
            return
        }
        
        //分隔符分为\n和\r,分开处理
        let infoItemsN = sourceStr.components(separatedBy: "\n")
        let infoItemsR = sourceStr.components(separatedBy: "\r")
        
        let infoItems = infoItemsN.count > infoItemsR.count ? infoItemsN:infoItemsR
        var taggedItems = Array<String>() //记录需要标红的信息
        showIndicator()
        DispatchQueue.global().async {
            let result = infoItems.map { [unowned self](item) -> String in
                let tmp = item.trimmingCharacters(in: .whitespacesAndNewlines)
                var interpretedStr = self.interpretCrashInfo(info: tmp)
                if let projectNameRange = interpretedStr.range(of: self.projectName) {
                    let offset = projectNameRange.upperBound.utf16Offset(in: interpretedStr)
                    let start = interpretedStr.index(interpretedStr.startIndex, offsetBy: offset)
                    let end = interpretedStr.index(interpretedStr.startIndex,offsetBy: interpretedStr.count)
                    interpretedStr = String(interpretedStr[start..<end]).trimmingCharacters(in: .whitespacesAndNewlines)
                    taggedItems.append(interpretedStr)
                }
                return interpretedStr
            }
            let resultStr = result.joined(separator: "\n")
            DispatchQueue.main.async {
                self.resultTextView.textStorage?.setAttributedString(NSAttributedString(string: resultStr))
                for item in taggedItems {
                    let range = resultStr.range(of: item)!
                    let location = resultStr.utf16.distance(from: resultStr.utf16.startIndex, to: range.lowerBound.samePosition(in: resultStr.utf16)!)
                    let length = resultStr.utf16.distance(from: range.lowerBound.samePosition(in: resultStr.utf16)!, to: range.upperBound.samePosition(in: resultStr.utf16)!)
                    let nsRange = NSMakeRange(location, length)
                    self.resultTextView.textStorage?.setAttributes([NSAttributedString.Key.foregroundColor: NSColor.red, NSAttributedString.Key.font:NSFont.systemFont(ofSize: 15)], range: nsRange)
                    
                }
                self.hideIndicator()
            }
        }
    }
    
    @IBAction func clear(_ sender: NSButton) {
        sourceTextView.textStorage?.setAttributedString(NSAttributedString(string: ""))
        resultTextView.textStorage?.setAttributedString(NSAttributedString(string: ""))
    }
    
    func interpretCrashInfo(info: String) -> String {
        guard info.count > 0 else {
            return info
        }
        // 3   无忧精英      0x0000000104349654 无忧精英 + 277461
        let local = info.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: " ")
        var mArray = local.filter{ $0 != "" }
        
                                                // 格式要求
        guard mArray.count == 6,                //1.共6部分
            isPureInt(str: mArray.first!),      //2.第1部分为纯数字
            mArray[1] == projectName else {     //3.第2部分为项目名
            return info
        }
        
        let stackAddress = mArray[2]    // 0x0000000104349654
        //计算baseAddress
        let offset = Int(mArray.last!)!
        
        let decimal = hexToDecimal(hex: stackAddress) - offset
        let baseAddress = String(decimal, radix: 16)
        
        //Mac app必须要启用App SandBox，但是在sandbox模式下，xcrun不允许使用，因此这里直接用atos而不用xcrun atos
        let commandStr = "atos -o \(dsymPath) -l \(baseAddress) \(stackAddress)"
        mArray[3] = runCommand(command: commandStr)
        return "\(mArray.first!) \(mArray[1])    \(mArray[3])"
        
    }
    
    func showIndicator() {
        indicator.isHidden = false
        indicator.startAnimation(nil)
    }
    
    func hideIndicator() {
        indicator.isHidden = true
        indicator.stopAnimation(nil)
    }
    
    func isPureInt(str: String) -> Bool {
        let scanner = Scanner(string: str)
        var val:Int = 0
        return scanner.scanInt(&val) && scanner.isAtEnd
    }
    
    func runCommand(command: String) -> String{
        let process = Process()
        process.launchPath = "/bin/bash"
        process.arguments = ["-c",command]
        
        let pipe = Pipe()
        let fileHandle = pipe.fileHandleForReading
        
        process.standardOutput = pipe
        process.launch()
        
        let data = fileHandle.readDataToEndOfFile()
        return String(data: data, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines)
    }
    
//MARK:- 进制转换
    // 十六进制 To 十进制
    func hexToDecimal(hex: String) -> Int{
        let scanner = Scanner(string: hex)
        var hex: UInt64 = 0
        scanner.scanHexInt64(&hex)
        return Int(hex)
    }
}

